package com.googlecode.YaccSyntaxTool.grammar;

import java.util.Set;

import com.googlecode.YaccSyntaxTool.export.Exporter;
import com.googlecode.YaccSyntaxTool.export.NotSupportedExportKindException;
import com.googlecode.YaccSyntaxTool.export.ReferenceMap;
import com.googlecode.YaccSyntaxTool.export.Exporter.ExportKind;

/**
 * This class represents one string in a term (factors are separated by spaces)
 * @author goe
 *
 */
public class Factor {
	private final FactorType	type;
	private final String		value;
	private final Expression	expression;


	private static final String KEYWORD_PREFIX	= "KW_";
	private static final String TOKEN_PREFIX		= "TK_";
	private static final String LITERAL_MARKER	= "'";


	public Factor(FactorType type, String value, Expression expression) {
		super();

		this.type		= type;
		this.value		= value;
		this.expression = expression;
	}

	public Factor(FactorType type, String value) {
		this(type,value,null);
	}

	public Factor(FactorType type, Expression expression) {
		this(type,null,expression);
	}

	/*
	 * Only supports BNF at the moment.
	 */
	public Factor(String iFactorStr, Set<String> excludes) {
		super();

		if (iFactorStr.startsWith(KEYWORD_PREFIX)) {
			type = FactorType.KEYWORD;
		} else if (iFactorStr.startsWith(TOKEN_PREFIX) || excludes.contains(iFactorStr)) {
			type = FactorType.TOKEN;
		} else if (iFactorStr.startsWith(LITERAL_MARKER) && iFactorStr.endsWith(LITERAL_MARKER)) {
			type = FactorType.LITERAL;
		} else {
			type = FactorType.NONTERMINAL; // points to another rule
		}

		switch (type) {
		case KEYWORD:
			value = iFactorStr.replace(KEYWORD_PREFIX,"").toLowerCase();
			break;
		case TOKEN:
			value = iFactorStr.replace(TOKEN_PREFIX,"").toUpperCase();
			break;
		default:
			value = iFactorStr;
			break;
		}
		expression = null;
	}

	public String toString() {
		String rv="";

		switch (type) {
		case NONTERMINAL:
		case KEYWORD:
		case TOKEN:
		case LITERAL:
			rv = value;
			break;
		case GROUP:
			rv = "( "+expression+" )";
			break;
		case OPTION:
			rv = "[ "+expression+" ]";
			break;
		case REPEAT:
			rv = "{ "+expression+" }";
			break;
		}
		return rv;
	}

	public FactorType getType() {
		return type;
	}

	public String getValue() {
		return value;
	}

	public Expression getExpression() {
		return expression;
	}

	public boolean equals(Factor factor) {
		if (type.equals(factor.getType())
			&& ((value == null && factor.getValue() == null) || value.equals(factor.getValue()))
			&& ((expression == null && factor.getExpression() == null) || expression.equals(factor.getExpression()))) {
			return true;
		} else {
			return false;
		}
	}

	/*
	 * EXPORT
	 */
	public void export(Exporter exporter) throws NotSupportedExportKindException {

		switch (exporter.getKind()) {
		case TXT:
			switch (type) {
			case NONTERMINAL:
			case KEYWORD:
			case TOKEN:
			case LITERAL:
				exporter.append(value);
				break;
			case GROUP:
				exporter.append("( ");
				expression.export(exporter);
				exporter.append(" )");
				break;
			case OPTION:
				exporter.append("[ ");
				expression.export(exporter);
				exporter.append(" ]");
				break;
			case REPEAT:
				exporter.append("{ ");
				expression.export(exporter);
				exporter.append(" }");
				break;
			}
			break;
		case HTML:
		case HTMLCOMPACT:
			switch (type) {
			case NONTERMINAL:

				if (value.startsWith("$@")) {
					/* do nothing, ignore on export */
				} else if (exporter.getKind() == ExportKind.HTMLCOMPACT) {
					exporter.append(value);
				} else {
					exporter.appendLink(value,value);
				}
				break;
			case KEYWORD:
				exporter.formatKeyword(value, true);
				break;
			case TOKEN:
				exporter.formatStr(value, false, true, "orange", true);
				break;
			case LITERAL:
				exporter.appendColored(value,"brown");
				break;
			case GROUP:
				exporter.append("( ");
				expression.export(exporter);
				exporter.append(" )");
				break;
			case OPTION:
				exporter.append("[ ");
				expression.export(exporter);
				exporter.append(" ]");
				break;
			case REPEAT:
				exporter.append("{ ");
				expression.export(exporter);
				exporter.append(" }");
				break;
			}
			break;
		default:
			throw new NotSupportedExportKindException(exporter.getKind());
		}
	}

	/*
	 * REFERENCES
	 */
	public void fillReferenceMap(ReferenceMap refMap, FactorType type, String refFrom) {
		if (this.type == type) {
			refMap.addReference(value, refFrom);
		}
	}

	/*
	 * SIMPLIFY
	 */

	// TODO

	/*
	 * EXTRACT
	 */
	public void fillSet(Set<String> set, FactorType type) {
		if (this.type == type) {
			set.add(value);
		}
	}
}
